﻿/*
关键设计解析：
   协议实现：
   完整支持 Modbus RTU 协议（功能码 01/02/03/04/06/10）
   大端序寄存器地址，小端序 CRC 校验
   严格遵循：地址→功能码→数据→CRC 的结构
   内存优化：
   使用 ArrayPool 重用字节数组，减少 GC 压力
   Span<T> 高效操作内存，避免额外拷贝
   MemoryMarshal 直接转换字节到 ushort 数组
   健壮性设计：
   全面的参数验证（地址范围、功能码、数据长度）
   完整的响应校验链（地址→功能码→数据长度→CRC）
   详细的异常处理和日志记录
   端序处理：
   csharp
   // 写入时：大端序
   BinaryPrimitives.WriteUInt16BigEndian(span.Slice(2, 2), begin);
   // 读取时：转换大端序数据
   var result = MemoryMarshal.Cast<byte, ushort>(buffer.Slice(3, number * 2));
   register = result.Select(BinaryPrimitives.ReverseEndianness).ToArray();
   位处理技巧：
   csharp
   // 位状态解析
   for (var i = 0; i < number; i++)
   {
       var byteIndex = 3 + i / 8;  // 数据从第3字节开始
       var bitIndex = i % 8;       // 位在字节中的位置
       register[i] = data[byteIndex].IsBitSet(bitIndex);
   }
   错误处理体系：
   设备返回异常码时解析具体原因
   超时、CRC错误、数据不匹配等分类处理
   使用 [MaybeNullWhen] 优化空值处理
   典型工作流程：
   写操作：
   A[构建请求帧] --> B[发送数据]
   B --> C[读取响应]
   C --> D{校验地址}
   D --> E{校验功能码}
   E --> F{校验数据}
   F --> G{校验CRC}
   G --> H[返回成功]
   读操作：
   A[发送读请求] --> B[接收响应头]
   B --> C{校验地址}
   C --> D{校验功能码}
   D --> E{校验数据长度}
   E --> F[接收剩余数据]
   F --> G{校验CRC}
   G --> H[解析数据]
   H --> I[返回结果]
   此实现提供了高效、可靠的 Modbus RTU 通信能力，适用于工业控制、IoT 设备等场景，通过内存池和 Span 优化特别适合高频次通信需求。
 */

using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Communication.Enums;
using Communication.Help.Crc;
using Communication.StreamResource;
using Iot.Shared.Helper;
using Microsoft.Extensions.Logging;
using NLogHelp;

namespace Communication.Help;

/// <summary>
///     Modbus RTU 协议通信辅助类 (优化版)
///     提供基于 RTU 格式的 Modbus 读写操作
/// </summary>
public static class ModbusRtuAlter
{
    /// <summary>
    ///     单寄存器写入指令长度
    /// </summary>
    private const int WriteSingleLength = 8;

    /// <summary>
    ///     包头长度 (地址+功能码+起始地址)
    /// </summary>
    private const int HeaderLength = 3;

    /// <summary>
    ///     包尾长度 (CRC校验)
    /// </summary>
    private const int FooterLength = 2;


    /// <summary>
    ///     包头包尾总长度
    /// </summary>
    private const int HeadFootCombinedLength = HeaderLength + FooterLength;

    private static readonly Crc16 Crc16 = new(Crc16Mode.Modbus);

    /// <summary>
    ///     使用数组池优化内存分配
    /// </summary>
    private static readonly ArrayPool<byte> ArrayPool = ArrayPool<byte>.Create();

    /// <summary>
    ///     构建单寄存器写入指令
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="function">功能码</param>
    /// <param name="begin">寄存器起始地址</param>
    /// <param name="value">要写入的值</param>
    public static void Write(this IStreamResource configuration, byte address, ModbusFunctionCode function,
        ushort begin, ushort value)
    {
        var data = ArrayPool.Rent(WriteSingleLength); // 从数组池租用缓冲区
        try
        {
            var span = data.AsSpan(0, WriteSingleLength);
            span[0] = address; // 字节1: 从站地址
            span[1] = (byte)function; // 字节2: 功能码
            BinaryPrimitives.WriteUInt16BigEndian(span.Slice(2, 2), begin); // 字节3-4: 寄存器起始地址(大端序)
            BinaryPrimitives.WriteUInt16BigEndian(span.Slice(4, 2), value); // 字节5-6: 要写入的值(大端序)

            BinaryPrimitives.WriteUInt16LittleEndian(span[^2..], Crc16.ComputeChecksum(span[..^2])); // 计算CRC并写入末尾 (小端序)
            configuration.Logger.LogInformation("发送数据：{数据}", NlogHexPackage.Create(data, WriteSingleLength));
            configuration.Write(data, 0, WriteSingleLength); // 发送数据
        }
        finally
        {
            ArrayPool.Return(data, true); // 归还数组到池
        }
    }


    /// <summary>
    ///     构建多寄存器写入指令
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="function">功能码</param>
    /// <param name="begin">寄存器起始地址</param>
    /// <param name="value">要写入的值</param>
    public static void Write(this IStreamResource configuration, byte address, ModbusFunctionCode function,
        ushort begin, params ushort[] value)
    {
        // 计算完整数据长度: 固定头 + 值数量*2 + 字节数标识
        var len = WriteSingleLength + value.Length * 2 + 1;
        var data = ArrayPool.Rent(len); // 从数组池租用缓冲区
        try
        {
            var span = data.AsSpan(0, len);
            span[0] = address; //1  从站地址
            span[1] = (byte)function; //2  功能码
            BinaryPrimitives.WriteUInt16BigEndian(span.Slice(2, 2), begin); // 3-4   寄存器起始地址
            BinaryPrimitives.WriteUInt16BigEndian(span.Slice(4, 2), (ushort)value.Length); // 5-6    寄存器数量  
            span[6] = (byte)(value.Length * 2); //7  字节数
            for (var i = 0; i < value.Length; i++) //写入所有数据值  (大端序)
            {
                BinaryPrimitives.WriteUInt16BigEndian(span.Slice(7 + i * 2, 2), value[i]);
            }

            BinaryPrimitives.WriteUInt16LittleEndian(span[^2..], Crc16.ComputeChecksum(span[..^2])); // 计算CRC并写入末尾 (小端序)
            configuration.Logger.LogInformation("发送数据：{数据}", NlogHexPackage.Create(data, len));
            configuration.Write(data, 0, len);
        }
        finally
        {
            ArrayPool.Return(data, true); // 归还数组到池
        }
    }

    /// <summary>
    ///     执行多寄存器写入 (功能码 0x10)
    /// </summary>
    /// <param name="configuration"> 通信资源对象</param>
    /// <param name="message">错误信息</param>
    /// <param name="address">从站地址</param>
    /// <param name="begin">寄存器起始地址</param>
    /// <param name="value">要写入的值</param>
    /// <returns>成功返回true, 失败返回false并输出错误信息</returns>
    public static bool WriteMultipleRegisters(this IStreamResource configuration, out string message, byte address,
        ushort begin,
        params ushort[] value)
    {
        // 定义功能码
        const ModbusFunctionCode function = ModbusFunctionCode.WriteMultipleRegisters;
        // 清空接收缓冲区
        configuration.DiscardInBuffer();
        // 发送指令
        configuration.Write(address, function, begin, value);
        // 从数组池租用缓冲区
        var data = ArrayPool.Rent(WriteSingleLength);

        try
        {
            // 将缓冲区转换为Span
            var buffer = data.AsSpan(0, WriteSingleLength);
            // 读取包头和包尾 (地址+功能码+CRC)
            configuration.ContinuousRead(data, 0, HeadFootCombinedLength);
            // 校验响应
            if (buffer[0] == address) // 地址匹配
            {
                if (buffer[1] == (byte)function) // 功能码匹配
                {
                    // 读取剩余数据 (寄存器地址+寄存器数量)
                    configuration.ContinuousRead(data, HeadFootCombinedLength,
                        WriteSingleLength - HeadFootCombinedLength);
                    // 地址校验
                    if (BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2, 2)) == begin)
                    {
                        // 数量校验
                        if (BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(4, 2)) == value.Length)
                        {
                            // CRC校验
                            if (BinaryPrimitives.ReadUInt16LittleEndian(buffer[^2..]) ==
                                Crc16.ComputeChecksum(buffer[..^2]))
                            {
                                // 所有校验通过
                                configuration.Logger.LogInformation("接收数据：{数据}",
                                    NlogHexPackage.Create(data, WriteSingleLength));
                                message = "写入成功";
                                return true;
                            }
                            else
                            {
                                // CRC校验错误
                                message = "Crc校验错误";
                            }
                        }
                        else
                        {
                            // 寄存器数量错误
                            message = "寄存器数量错误";
                        }
                    }
                    else
                    {
                        // 寄存器地址错误
                        message = "寄存器地址错误";
                    }
                }
                else // 功能码不匹配（可能是异常响应）
                {
                    // 解析异常响应
                    message = ModbusExceptionCodeAnalytic(function, buffer);
                }
            }
            else // 地址不匹配
            {
                // 地址错误
                message = "地址错误";
            }

            // 记录错误日志
            configuration.Logger.LogError("接收数据：{信息}-{数据}", message, NlogHexPackage.Create(data, WriteSingleLength));
        }
        catch (Exception ex)
        {
            // 异常处理
            message = configuration.ExceptionAnalytic(ex, data, WriteSingleLength);
        }
        finally
        {
            // 归还数组到池
            ArrayPool.Return(data, true);
        }

        return false;
    }

    /// <summary>
    ///     执行单寄存器写入 (功能码 0x06)
    /// </summary>
    /// <param name="configuration"> 通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="begin">寄存器起始地址</param>
    /// <param name="value">写入值</param>
    /// <param name="message">失败原因</param>
    public static bool WriteSingleRegister(this IStreamResource configuration, byte address, ushort begin, ushort value,
        out string message)
    {
        // 定义Modbus功能码
        const ModbusFunctionCode function = ModbusFunctionCode.WriteSingleRegister;
        // 清空输入缓冲区
        configuration.DiscardInBuffer();
        // 向配置中写入地址、功能码、起始地址和值
        configuration.Write(address, function, begin, value);
        // 从ArrayPool中租用WriteSingleLength长度的数组
        var data = ArrayPool.Rent(WriteSingleLength);

        try
        {
            // 将数组转换为Span
            var buffer = data.AsSpan(0, WriteSingleLength);
            // 从配置中连续读取数据
            configuration.ContinuousRead(data, 0, HeadFootCombinedLength);
            // 如果接收到的数据的第一个字节与地址相同
            if (buffer[0] == address)
            {
                // 如果接收到的数据的第二个字节与功能码相同
                if (buffer[1] == (byte)function)
                {
                    // 从配置中连续读取数据
                    configuration.ContinuousRead(data, HeadFootCombinedLength,
                        WriteSingleLength - HeadFootCombinedLength);
                    // 如果接收到的数据的第三个字节和第四个字节与起始地址相同
                    if (BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2, 2)) == begin)
                    {
                        // 如果接收到的数据的第五个字节和第六个字节与值相同
                        if (BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(4, 2)) == value)
                        {
                            // 如果接收到的数据的最后两个字节与前面数据的CRC16校验码相同
                            if (BinaryPrimitives.ReadUInt16LittleEndian(buffer[^2..]) ==
                                Crc16.ComputeChecksum(buffer[..^2]))
                            {
                                // 记录接收到的数据
                                configuration.Logger.LogInformation("接收数据：{数据}",
                                    NlogHexPackage.Create(data, WriteSingleLength));
                                // 将message设置为null
                                message = "写入成功";
                                // 返回true
                                return true;
                            }
                            else
                            {
                                // CRC校验错误
                                message = "Crc校验错误";
                            }
                        }
                        else
                        {
                            // 写入值错误
                            message = "写入值错误";
                        }
                    }
                    else
                    {
                        // 寄存器地址错误
                        message = "寄存器地址错误";
                    }
                }
                else
                {
                    // 功能码错误
                    message = ModbusExceptionCodeAnalytic(function, buffer);
                }
            }
            else
            {
                // 地址错误
                message = "地址错误";
            }

            // 记录错误信息
            configuration.Logger.LogError("接收数据：{信息}-{数据}", message, NlogHexPackage.Create(data, WriteSingleLength));
        }
        catch (Exception ex)
        {
            // 异常处理
            message = configuration.ExceptionAnalytic(ex, data, WriteSingleLength);
        }
        finally
        {
            // 将数组归还给ArrayPool
            ArrayPool.Return(data, true);
        }

        // 返回false
        return false;
    }


    /// <summary>
    ///     异常处理与日志记录
    /// </summary>
    /// <param name="configuration"></param>
    /// <param name="ex"></param>
    /// <param name="data"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    private static string ExceptionAnalytic(this IStreamResource configuration, Exception ex, IEnumerable<byte> data,
        int length)
    {
        var message = ex switch
        {
            // 特殊处理超时异常
            TimeoutException => "接收超时没有可供读取的数据",
            _ => ex.Message
        };
        configuration.Logger.LogError("接收数据：{信息}-{数据}", message, NlogHexPackage.Create(data, length));
        return message;
    }

    /// <summary>
    ///     解析Modbus异常码
    /// </summary>
    /// <param name="function"></param>
    /// <param name="data"></param>
    /// <returns></returns>
    private static string ModbusExceptionCodeAnalytic(ModbusFunctionCode function, Span<byte> data)
    {
        data = data[..5]; //异常响应固定 5 字节
        //检查是否为异常响应(功能码高位为1)
        if ((byte)((byte)function | 0x80) != data[1]) return "功能码错误";
        // CRC校验
        if (BinaryPrimitives.ReadUInt16LittleEndian(data[^2..]) != Crc16.ComputeChecksum(data[..^2])) return "Crc校验错误";
        var exceptionCodeByte = data[2]; // 获取异常码并转换为描述信息
        return ((ModbusExceptionCode)exceptionCodeByte).GetDisplayName();
    }

    #region 线圈/离散量读取‌

    /// <summary>
    ///     读取线圈或离散量状态
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="function">功能码</param>
    /// <param name="begin">寄存器起始地址</param>
    /// <param name="number">读取寄存器数量</param>
    /// <param name="register">读取内容</param>
    /// <returns>true 读取成功  false 读取失败</returns>
    public static bool ModbusRead(this IStreamResource configuration, byte address, ModbusFunctionCode function,
        ushort begin, ushort number, [MaybeNullWhen(false)] out bool[] register)
    {
        // 判断功能码是否支持
        if (function != ModbusFunctionCode.ReadCoils && function != ModbusFunctionCode.ReadDiscreteInputs)
            throw new ArgumentOutOfRangeException(nameof(function), function, $@"不支持{function}功能码。");
        // 判断读取点数是否在1-2000之间
        if (number is < 1 or > 2000)
            throw new ArgumentException("读取点数必须在1-2000之间");
        // 清空输入缓冲区
        configuration.DiscardInBuffer();
        // 发送读取指令
        configuration.Write(address, function, begin, number);
        // 计算数据长度
        var dataLength = (int)Math.Ceiling(number / 8f);

        // 计算总长度
        var length = HeadFootCombinedLength + dataLength;
        // 从内存池中租借内存
        var data = ArrayPool.Rent(length);
        try
        {
            // 将内存转换为Span
            var buffer = data.AsSpan(0, length);
            // 连续读取数据
            configuration.ContinuousRead(data, 0, HeadFootCombinedLength);
            string mse;
            // 判断地址是否正确
            if (buffer[0] == address)
            {
                // 判断功能码是否正确
                if (buffer[1] == (byte)function)
                {
                    // 判断数据长度是否正确
                    if (buffer[2] == dataLength)
                    {
                        // 连续读取数据
                        configuration.ContinuousRead(data, HeadFootCombinedLength, length - HeadFootCombinedLength);
                        // 判断CRC校验是否正确
                        if (BinaryPrimitives.ReadUInt16LittleEndian(buffer[^2..]) ==
                            Crc16.ComputeChecksum(buffer[..^2]))
                        {
                            // 记录接收数据
                            configuration.Logger.LogInformation("接收数据：{数据}",
                                NlogHexPackage.Create(data, length));

                            // 初始化寄存器数组
                            register = new bool[number];
                            // 遍历寄存器数组
                            for (var i = 0; i < number; i++)
                            {
                                // 计算字节索引和位索引
                                var byteIndex = HeaderLength + i / 8;
                                var bitIndex = i % 8;
                                // 设置寄存器值
                                register[i] = data[byteIndex].IsBitSet(bitIndex);
                            }

                            return true;
                        }
                        else
                        {
                            mse = "Crc校验失败";
                        }
                    }
                    else
                    {
                        mse = "接收数据长度错误";
                    }
                }
                else
                {
                    mse = ModbusExceptionCodeAnalytic(function, buffer);
                }
            }
            else
            {
                mse = "地址错误";
            }

            // 记录错误信息
            configuration.Logger.LogError("接收数据：{信息}-{数据}", mse, NlogHexPackage.Create(data, length));
        }
        catch (Exception ex)
        {
            // 异常分析
            configuration.ExceptionAnalytic(ex, data, length);
        }
        finally
        {
            // 返回内存池
            ArrayPool.Return(data, true);
        }

        // 设置寄存器数组为null
        register = null;
        return false;
    }


    /// <summary>
    ///     功能码01 (0x01) 读线圈
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="startAddress">寄存器起始地址</param>
    /// <param name="number">读取寄存器数量</param>
    /// <param name="register">读取内容</param>
    /// <returns>true 读取成功  false 读取失败</returns>
    public static bool ReadCoils(this IStreamResource configuration, byte address, ushort startAddress,
        ushort number, [MaybeNullWhen(false)] out bool[] register)
    {
        return configuration.ModbusRead(address, ModbusFunctionCode.ReadCoils, startAddress, number, out register);
    }


    /// <summary>
    ///     功能码02 (0x02) 读离散输入
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="startAddress">寄存器起始地址</param>
    /// <param name="number">读取寄存器数量</param>
    /// <param name="register">读取内容</param>
    /// <returns>true 读取成功  false 读取失败</returns>
    public static bool ReadDiscreteInputs(this IStreamResource configuration, byte address, ushort startAddress,
        ushort number, [MaybeNullWhen(false)] out bool[] register)
    {
        return configuration.ModbusRead(address, ModbusFunctionCode.ReadDiscreteInputs, startAddress, number,
            out register);
    }

    #endregion

    #region 寄存器读取

    /// <summary>
    ///     功能码03 (0x03) 读保持寄存器
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="startAddress">寄存器起始地址</param>
    /// <param name="number">读取寄存器数量</param>
    /// <param name="register">读取内容</param>
    /// <returns>true 读取成功  false 读取失败</returns>
    public static bool ReadHoldingRegisters(this IStreamResource configuration, byte address, ushort startAddress,
        ushort number, [MaybeNullWhen(false)] out ushort[] register)
    {
        return configuration.ModbusRead(address, ModbusFunctionCode.ReadHoldingRegisters, startAddress, number,
            out register);
    }

    /// <summary>
    ///     功能码04 (0x04) 读输入寄存器
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="startAddress">寄存器起始地址</param>
    /// <param name="number">读取寄存器数量</param>
    /// <param name="register">读取内容</param>
    /// <returns>true 读取成功  false 读取失败</returns>
    public static bool ReadInputRegisters(this IStreamResource configuration, byte address, ushort startAddress,
        ushort number, [MaybeNullWhen(false)] out ushort[] register)
    {
        return configuration.ModbusRead(address, ModbusFunctionCode.ReadInputRegisters, startAddress, number,
            out register);
    }


    /// <summary>
    ///     读取保持寄存器或输入寄存器
    /// </summary>
    /// <param name="configuration">通信资源对象</param>
    /// <param name="address">从站地址</param>
    /// <param name="function">功能码</param>
    /// <param name="begin">寄存器起始地址</param>
    /// <param name="number">读取寄存器数量</param>
    /// <param name="register">读取内容</param>
    /// <returns>true 读取成功  false 读取失败</returns>
    public static bool ModbusRead(this IStreamResource configuration, byte address, ModbusFunctionCode function,
        ushort begin, ushort number, [MaybeNullWhen(false)] out ushort[] register)
    {
        // 判断功能码是否支持
        if (function != ModbusFunctionCode.ReadHoldingRegisters && function != ModbusFunctionCode.ReadInputRegisters)
            throw new ArgumentOutOfRangeException(nameof(function), function, $"不支持{function}功能码。");
        // 判断读取数量是否在1-125之间
        if (number is < 1 or > 125)
            throw new ArgumentOutOfRangeException(nameof(number), number, "读取数量必须在1-125之间");
        // 清空输入缓冲区
        configuration.DiscardInBuffer();
        // 发送读取命令
        configuration.Write(address, function, begin, number);
        // 计算数据长度
        var dataLength = number * 2;
        //1:从站地址  2:功能码  3:字节数  X: 数据长度   X+1:Crc低    X+2:Crc高
        var length = HeadFootCombinedLength + dataLength;
        // 从内存池中租借一块内存
        var data = ArrayPool.Rent(length);
        try
        {
            // 将内存块转换为Span
            var buffer = data.AsSpan(0, length);
            // 读取数据头
            configuration.ContinuousRead(data, 0, HeadFootCombinedLength);
            string mse;
            // 判断从站地址是否正确
            if (buffer[0] == address)
            {
                // 判断功能码是否正确
                if (buffer[1] == (byte)function)
                {
                    // 判断数据长度是否正确
                    if (buffer[2] == dataLength)
                    {
                        // 读取数据
                        configuration.ContinuousRead(data, HeadFootCombinedLength, length - HeadFootCombinedLength);
                        // 校验Crc
                        if (BinaryPrimitives.ReadUInt16LittleEndian(buffer[^2..]) ==
                            Crc16.ComputeChecksum(buffer[..^2]))
                        {
                            // 记录日志
                            configuration.Logger.LogInformation("接收数据：{数据}",
                                NlogHexPackage.Create(data, length));
                            // 将数据转换为ushort数组
                            var result = MemoryMarshal.Cast<byte, ushort>(buffer.Slice(HeaderLength, number * 2));
                            // 反转字节序
                            register = result.Select(BinaryPrimitives.ReverseEndianness).ToArray();
                            return true;
                        }
                        else
                        {
                            mse = "Crc校验失败";
                        }
                    }
                    else
                    {
                        mse = "接收数据长度错误";
                    }
                }
                else
                {
                    mse = ModbusExceptionCodeAnalytic(function, buffer);
                }
            }
            else
            {
                mse = "地址错误";
            }

            // 记录错误日志
            configuration.Logger.LogError("接收数据：{信息}-{数据}", mse, NlogHexPackage.Create(data, length));
        }
        catch (Exception ex)
        {
            // 异常分析
            configuration.ExceptionAnalytic(ex, data, length);
        }
        finally
        {
            // 将内存块归还给内存池
            ArrayPool.Return(data, true);
        }

        // 设置寄存器数据为null
        register = null;
        return false;
    }

    #endregion
}